/*Copyright (C) 2006 Gyowanny Queiroz.
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:

 1. Redistributions of source code must retain the above copyright
 notice, this list of conditions, and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions, and the disclaimer that follows
 these conditions in the documentation and/or other materials provided
 with the distribution.

 3. The end-user documentation included with the redistribution,
 if any, must include the following acknowledgment:
 "This product includes software developed by the
 Bean2SQL Project (http://code.google.com/p/bean2sql/)."
 Alternately, this acknowledgment may appear in the software itself,
 if and wherever such third-party acknowledgments normally appear.

 4. The name "Bean2SQL" must not be used to endorse or promote products
 derived from this software without prior written permission. For
 written permission, please contact <gyowanny_AT_gmail_DOT_com>

 5. Products derived from this software may not be called "Bean2SQL",
 nor may "Bean2SQL" appear in their name, without prior written
 permission from the Swixml Project Management.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED.  IN NO EVENT SHALL THE SWIXML PROJECT OR ITS
 CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 SUCH DAMAGE.
 ====================================================================

 This software consists of voluntary contributions made by many
 individuals on behalf of the Bean2SQL Project and was originally
 created by Gyowanny Queiroz <gyowanny_AT_gmail_DOT_com>. For more information
 on the Bean2SQL Project, please see <http://code.google.com/p/bean2sql/>.
 */
package org.bean2sql;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.bean2sql.annotations.Bean2SqlField;
import org.bean2sql.annotations.Bean2SqlPk;

/**
 * 
 * @author Gyowanny Queiroz
 * 
 */
public class DefaultBeanParser implements BeanParser {
    public static final String NULL_VALUE = "null";

    public static final String GETTER_PREFIX = "get";

    public static final String WHERE_SEPARATOR = " and ";

    private BeanSqlGenerator beanSqlGenerator;

    private Map<String, PkField> pkFields = new HashMap<String, PkField>();

    public DefaultBeanParser(BeanSqlGenerator beanSqlGenerator) {
        this.beanSqlGenerator = beanSqlGenerator;
    }

    public Map<String, String> getValueMap(Object bean) throws Exception {
        LinkedHashMap<String, String> values = new LinkedHashMap<String, String>();
        for (Field field : bean.getClass().getDeclaredFields()) {

            Object value = this.getFieldValue(bean, field);
            String strValue = NULL_VALUE;
            if (value != null) {
                strValue = value.toString();
            }

            if (value instanceof Date) {
                strValue = "'" + this.formatDateValue((Date) value) + "'";
            } else if (value instanceof String) {
                strValue = "'" + strValue + "'";
            }

            String fieldName = field.getName();

            Bean2SqlField fieldAnnotation = field
                    .getAnnotation(Bean2SqlField.class);
            if (fieldAnnotation != null) {
                fieldName = fieldAnnotation.field();
            }

            Bean2SqlPk pkAnnotation = field.getAnnotation(Bean2SqlPk.class);
            // verifying if the field is primary key
            if (pkAnnotation != null) {
                fieldName = (pkAnnotation.field().trim().equals("") ? field
                        .getName() : pkAnnotation.field());
                this.pkFields.put(field.getName(), new PkField(field.getName()
                        .toLowerCase(), fieldName, value));
                fieldName = BeanParser.PK_IDENTIFIER
                        + fieldName;
                if (pkAnnotation.isAutoIncrement()){
                    strValue = NULL_VALUE;
                }
            }
            values.put(fieldName, strValue);
        }
        if (this.pkFields.size() > 0) {
            values.put(BeanParser.PK_FIELDS_KEY, this.getWhereClauseForPks());
        }
        return values;
    }

    public List<String> getMetadataMap(Object bean) throws Exception {
        List<String> metadata = new LinkedList<String>();
        for (Field field : bean.getClass().getDeclaredFields()) {
            FieldMetaData fmd = new FieldMetaData();

            Bean2SqlField fieldAnnotation = field
                    .getAnnotation(Bean2SqlField.class);
            if (fieldAnnotation != null) {
                fmd
                        .setFieldName(fieldAnnotation.field().trim().equals("") ? field
                                .getName()
                                : fieldAnnotation.field());
                fmd.setFieldType(fieldAnnotation.type());
                fmd.setFieldLength(fieldAnnotation.length());
            }

            Bean2SqlPk fieldPkAnnotation = field
                    .getAnnotation(Bean2SqlPk.class);
            if (fieldPkAnnotation != null) {
                fmd
                        .setFieldName(fieldPkAnnotation.field().trim().equals(
                                "") ? field.getName() : fieldPkAnnotation
                                .field());
                fmd.setFieldType(fieldPkAnnotation.type());
                fmd.setFieldLength(fieldPkAnnotation.length());
                fmd.setPrimaryKey(true);
            }

            metadata.add(fmd.getDeclaration());
        }
        return metadata;
    }

    private String getWhereClauseForPks() {
        String sqlValues = "";

        Iterator<String> keys = pkFields.keySet().iterator();
        while (keys.hasNext()) {
            PkField field = pkFields.get(keys.next());
            sqlValues += field.getTableFieldName() + " = " + field.getValue()
                    + WHERE_SEPARATOR;
        }

        sqlValues = sqlValues.substring(0, sqlValues
                .lastIndexOf(WHERE_SEPARATOR));

        return " WHERE " + sqlValues;
    }

    private String formatDateValue(Date value) {
        String pattern = this.beanSqlGenerator.getDateFormat();
        if (value == null) {
            return "";
        }
        if (pattern == null || pattern.trim().equals("")) {
            return SimpleDateFormat.getDateInstance().format(value);
        }
        return new SimpleDateFormat(pattern).format(value);
    }

    private Object getFieldValue(Object bean, Field field) {
        String name = GETTER_PREFIX + field.getName().toLowerCase();
        for (Method method : bean.getClass().getMethods()) {

            if (method.getName().toLowerCase().equals(name)) {
                try {
                    Object value = method.invoke(bean, new Object[0]);
                    return value;
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
                return "";
            }
        }
        return null;
    }

    class PkField {

        private String fieldName;

        private String tableFieldName;

        private Object value;

        public PkField(String fieldName, String tableFieldName, Object value) {
            this.fieldName = fieldName;
            this.tableFieldName = tableFieldName;
            this.value = value;
        }

        public String getFieldName() {
            return fieldName;
        }

        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        public String getTableFieldName() {
            return tableFieldName;
        }

        public void setTableFieldName(String tableFieldName) {
            this.tableFieldName = tableFieldName;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return fieldName + " -> " + tableFieldName;
        }

    }

    class FieldMetaData {
        private String fieldName;

        private String fieldType;

        private int fieldLength;

        private boolean primaryKey;

        public FieldMetaData() {

        }

        public FieldMetaData(String fieldName, String fieldType,
                int fieldLength, boolean primaryKey) {
            this.fieldName = fieldName;
            this.fieldType = fieldType;
            this.fieldLength = fieldLength;
            this.primaryKey = primaryKey;
        }

        public int getFieldLength() {
            return fieldLength;
        }

        public void setFieldLength(int fieldLength) {
            this.fieldLength = fieldLength;
        }

        public String getFieldName() {
            return fieldName;
        }

        public void setFieldName(String fieldName) {
            this.fieldName = fieldName;
        }

        public String getFieldType() {
            return fieldType;
        }

        public void setFieldType(String fieldType) {
            this.fieldType = fieldType;
        }

        public boolean isPrimaryKey() {
            return primaryKey;
        }

        public void setPrimaryKey(boolean primaryKey) {
            this.primaryKey = primaryKey;
        }

        public String getDeclaration() {
            String dec = "";

            dec = fieldName + " " + fieldType;
            if (fieldLength > 0) {
                dec += "(" + fieldLength + ")";
            }
            if (isPrimaryKey()) {
                dec += " PRIMARY KEY";
            }

            return dec;
        }
    }

}
